Introduction.
Changebars are used to mark modified parts in existing documents. For the usage in TEX documents, there exist only solutions that use driver/printer features by the way of inserting | commands in the TEX source, e.g. for POSTSCRIPT drivers. This results in documents that are no longer as freely interchangeable as the DVI concept would allow—device dependency is problematic especially for this application that is useful for multi-authoring or standards development.
This macro package offers a pure TEX solution. Nevertheless, it has its restrictions, too. The page break will no longer be optimal, because no strechability or shrinkability on a page on top of the last mark of a change is supported. But this seems to be acceptable, especially as the change bar feature often will be used for proof reading and not in the final document. This restriction is the reason why no change marks can be used on titlepages or for similar constructions. Changes in floating insertions (footnotes, figures) are not discovered.
The demonstrated solution is written in Plain TEX, because it was easier and could be presented better at the EuroTEX89 conference in Karlsruhe. An adaption to LATEX is possible, but would require modifications to the LATEX kernel, i.e. the output routine and the layout parameters of the standard styles.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. See the GNU General Public License for more details.
If you have not received a copy of the GNU General Public License along with this program, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
A changed area is described by two marks, |
_penalty=_penalty_group_penalty_begin0
_output
| and
|_space=
_penalty=_penalty_group_penalty_end0
_output
_space
|. The method for writing a change bar consists of three
parts: First, it will be indicated to the output routine that a change
has occured. Next, the position of the changed area will be found out
and fixed. A list of all changed areas is built, that can at last be
marked by the output routine using vertical rules at the right margin.
But before we start we declare some shorthands for category codes. By declaring the at sign (`|@|') as well as the underscore `(|_|)' as letters we can use them in our macros. (I agree with D. Knuth that |_several_words_long| is more readable than || and in every case better than |@@@s|.) With the at sign we can use the ``private'' Plain macros and with the underscore we can make our own macros more readable. But as we have to restore these category codes at the end of this macro file we store their former values in the control sequences || and ||. This method is better than to use a group because not all macros have to be defined global this way.
=11
=` =`_
`= `_=
Triggering the Output Routine.
First, the output routine must know that changes occured. This is done
by the macros |
_penalty=_penalty_group_penalty_begin0
_output
| and |_space=
_penalty=_penalty_group_penalty_end0
_output
_space
| that append a reserved
penalty value |
_penalty| to the current list that is below
-10 000. The penalty will be in the range
|enaltygroup|⋅100 - 99…|
enaltygroup|⋅100.
For the output it must be differentiated if the mark indicates
begin or end of the changed area. This is done with the values
|_penalty_begin| and |_penalty_end| that are used as the
second-to-last digit of the change penalty.
It must also be differentiated between the beginning of a mark being in horizontal or vertical mode. In horizontal mode, the change bar must not begin at the baseline of the actual text position, but on top of the actual line. This is marked in the last digit, an odd digit will be used in horizontal mode.
Note that the values mentioned above are used as digits here that can be concatenated. If they are not followed by an other digit they should be terminated by | | to stop the look-ahead for digits.
These interactions with the output routine adds a lot of dead cycles; we must therefore increase || to prevent TEX from grumbling.
=100
The marks set the change penalty value (including the indication of begin or end of mark). The rest (|_output|) is the same action for both. An end change mark in horizontal mode can be preceeded by glue that could cause a line break, thus including the following line to the change area as well. To avoid this unwanted behaviour, the space is saved in |_space|, discarded in front of the mark and restored afterwards.
_penalty
_space
In |_output| the real output trigger is done. We must consider
that an ||-invocation by our |
_penalty| can be discarded at
the beginning of a page. So we trigger the output routine twice, first
with a special penalty value that is 2 less than the correct value
(including the code for horizontal or vertical mode). After the first
page break it is asserted that the current list is empty. The output
routine has to save the former page contents if necessary.
Now we set the penalty to the correct value. The second page break does the real work, restores the page contents and handles the split insertions (footnotes, figures, ...)
In horizontal mode || must not be destroyed.
_space_factor
The usage of the output routine as an information passing system has always it's difficulties. One of the real hard parts is the handling of the page marks, special token lists that are handled by TEX. These token lists provide informations about text on a page, they are usually used to create head lines etc. The user may access these token lists with three control sequences: || is the last page mark given, || is the || of the previous page, and || is the first page mark on the actual page or || if none was given. Here ``page'' is used in the TEX sense, i.e. as the material which has been collected between two || invocations. Of course, the page marks must not be destroyed—and that means they must be reinserted after each special use of the output routine.
But we have luck: A ``special use'' consists of two || invocations, so we can insert || again as a page mark after the first invocation where it will be the only page mark on that TEX page. The second invocation will automatically transform this page mark into the ``last page mark on the previous page,'' i.e. in ||—that's what we need! Furthermore || and || are saved in control sequences during the first invocation, they will be inserted again, too.
There's one situation where this approach doesn't work: In front of
the first page mark ||, ||, and || expand
to an empty token list. If we save them then and insert their old
values we have inserted empty page marks. If other page marks follow
on the same ``real'' page || will be empty instead of
expanding to the token list of the first page mark. To prevent this
we must not save and restore page marks before the first |
save_mark@true
_penalty=
save_mark@true
_penalty=mark_penalty_group00 _output
mark=@mark
@mark
_penalty_group00 _output
mark=@mark
@mark
| has
been added to the main vertical list.
Well, that can be controlled with a switch—but this switch must be
set very carefully. If it is set immediately by the first |
save_mark@true
_penalty=
save_mark@true
_penalty=mark_penalty_group00 _output
mark=@mark
@mark
_penalty_group00 _output
mark=@mark
@mark
|
this may be in horizontal mode and special output invocations can
occur above this page mark (i.e., there may be a |
_penalty=_penalty_group_penalty_begin0
_output
| in the
same paragraph in front of the |
save_mark@true
_penalty=
save_mark@true
_penalty=mark_penalty_group00 _output
mark=@mark
@mark
_penalty_group00 _output
mark=@mark
@mark
|). Therefore the setting of the
switch must be delayed until the vertical position of the |
save_mark@true
_penalty=
save_mark@true
_penalty=mark_penalty_group00 _output
mark=@mark
@mark
_penalty_group00 _output
mark=@mark
@mark
|
(precisely: the position of the |
save_mark@true
_penalty=
save_mark@true
_penalty=mark_penalty_group00 _output
mark=@mark
@mark
_penalty_group00 _output
mark=@mark
@mark
| in the current list) is
reached. In horizontal mode this can be done with a || and
the output routine! Voilà, this is another command group for the
output routine with only one command.
@save_mark@ save_mark@false
We will redefine |
save_mark@true
_penalty=
save_mark@true
_penalty=mark_penalty_group00 _output
mark=@mark
@mark
_penalty_group00 _output
mark=@mark
@mark
| so that the first page mark either sets
the switch to true (in vertical mode all possible special page breaks
are already handled) or forces the || routine to do this at an
appropriate place. In the last case we can use |_output|
again. Afterwards we restore the original meaning of |
save_mark@true
_penalty=
save_mark@true
_penalty=mark_penalty_group00 _output
mark=@mark
@mark
_penalty_group00 _output
mark=@mark
@mark
| again to
reduce the processing overhead (and the dead cycles).
This change of |
save_mark@true
_penalty=
save_mark@true
_penalty=mark_penalty_group00 _output
mark=@mark
@mark
_penalty_group00 _output
mark=@mark
@mark
| has the consequence that the first |
save_mark@true
_penalty=
save_mark@true
_penalty=mark_penalty_group00 _output
mark=@mark
@mark
_penalty_group00 _output
mark=@mark
@mark
| in a
document cannot be used anymore in horizontal mode inside a vertical
box that shall be split afterwards. But this is only sensible if this
mark shall be used as || because it will almost never
migrate to the outer list—really a rare case!
@mark=
save_mark@true
_penalty=
save_mark@true
_penalty=mark_penalty_group00 _output
mark=@mark
@mark
_penalty_group00 _output
mark=@mark
@mark
If the output routine is triggered with the mark penalty value it will call |_saving_page_marks|.
To finish the treatment of page marks we can formulate the two macros which are used at the first resp. second invocation of a ``special output,'' the principles have already been explained.
Positioning the Change Marks.
Now we must handle the positions of the bars. |_pos| will hold the position of the actual mark, i.e. the distance between top of page and actual mark. |&top#top;_change_pos| will hold the beginning of a changed area; a value of || indicates that no change is in effect. If a changed area is completed, it is appended to the list |_̄list| as an element |(̄&top#top;_change_pos,_pos)|. This list contains all changed areas within the current page so that bars can be written later on. A single bar will be produced by |ar|.
The definition of ||̄ to || allows the concatenation of new elements to |_̄list| with |
If the output routine was activated by a || value within the range of our reserved penalties, the change handling will occur, otherwise standard plain output can be done.
_group
==
_group=_group by 100
_group=_penalty_group _handling
_group=
save_mark@true
_penalty=
save_mark@true
_penalty=mark_penalty_group00 _output
mark=@mark
@mark
_penalty_group00 _output
mark=@mark
@mark
_penalty_group
_saving_page_marks
As explained before, the change handling must differentiate
between the kind of the change command (beginning is indicated by
|md| = 0, end by 1) and between the mode (horizontal indicated
by an odd |_mode| value, vertical by an even). A change mode
higher than one indicates that we are doing the first page break that
has to backup the page as far as it exists already and results in an
empty current list of page elements.
_cmd _mode
Processing a mark during the second trigger of the output routine means restoring the page and storing the positions. At the beginning, the begin of the change is saved, at the end, we know the bar already and put it into the bar list. Then the positioning values are reinitialized.
As within every output invocation, the box 255 must be unboxed. As we are here in the second invocation of the output routine the |255| consists only of the empty || we have inserted in |_output|. We can therefore throw it away.
Handling the Page Contents.
We handle the part of the page that was collected up to now by putting it into a box. This fixes the position of the change mark so that |_pos| can be set and stored later on in |&top#top;_change_pos| or as the lower end of a bar in |_̄list|.
To save the page contents in the first output invocation, we have to
save the page in |_page|. Before that, we store the size of the
box (which equals ||!) in |_goal|. If the unboxing
caused an increase of height (i.e. if
|| > |
|),
we eject the page up to the change mark. Now we have to compute the
current position of our mark in |_pos|. It is fixed by the size
of the |_page|, but in case of a begin mark in horizontal mode we
must decrease it from the baseline position to the top of the last line.
Finally, we must save the values for the allowed insertions and change
them to the maximal value so that a rest that is split from an insertion
will be appended to the insertion box at the second invocation in every
case.
The || is initialized to ||. This allows to control whether this first output invocation ocurred or if it was discarded. For the same reason |_pos| is initialized to 0pt.
_page
_goal _vsize _vsize= _dimen_topins _dimen_footins
_pos=0pt
To eject a page as far as it is we restore it from the |_page| back to box 255. In horizontal mode and at a begin mark the last line contains the mark and must not be output. So we remove it and the preceding glue from the stored rest, just leaving a single hbox to be on top of the actual page (in |_page|) now. Then normal output can be done with box 255.
In horizontal mode and at a begin mark, we need the position of the mark (|_pos|) on the upper boundary of the last line in |_page|. If there is just one line left from a recent eject, the height is given by the topskip decreased by the height of this hbox. If the height of the box is larger than || the skip will not be inserted and the change position results to 0pt. Otherwise, |_page| is a vbox whose last hbox we delete temporarily using box 0. Height and depth of the rest are the actual position on the page.
The double of the page we have constructed this way will immediately be fed back to the garbage collector because it could have become reasonably large.
To restore a page during the second output invocation, we first restore the saved values, but only if they were really changed (this can be discovered by the value of |_vsize|). Now the |_page| is appended to the current list as a box, which stops later usage of its stretch- and shrinkability! Then the collected insertions can be inserted again. The page marks have to be inserted, too.
Please note, that there is still a problem with this concept of handling the output trigger:
If the first output trigger is discarded because a page break has occured just in front, footnote parts may be juggled around. I.e., if a footnote is split in three parts, the first part was just been shipped out, the second part is inserted back into the recent contributions by the output routine but behind the third part which is saved in the ``special place'' (according to the TEXbook, p. 125). A solution to this problem might be to insert a |_change| again within the second output trigger and finishing the treatmend afterwards. Afterwards a full triggering process (two output invocations) is executed again and alle insertion parts will be accessible in the insertion box.
By the way, the almost same problem appears in LATEX, too. Almost: in LATEX this can happen every time because at the first output invocation the ||-values of the footnote insertion is not increased. I leave the problem open to the reader ...
Writing the Stuff.
The positions of the bars which mark the changed areas are relative to the top of the text, i.e. the height of the top insertion is not included. Therefore it is best to write them just after the top insertions before the page text—but to do this we have to change the Plain macro | _current_bar ll_bars @=cclv cclv @ggedbottom -@ |.
Below is the new definition, I have just rearranged it a little bit so that it is more legible. The line I have inserted is marked with `|if a changed area is not yet finished, afterwards all bars can be written.
If
|hangepos| = |
| no change is active. Otherwise
the current change reaches from the begin mark (|&top#top;_change_pos|) to
the end of the page, i.e. we insert a virtual end mark. Because the
change continues on the next page we insert a virtual begin mark on the
top of the page, too.
Now we can write all bars—if they exist anyway. It's rather easy, we just have to define ||̄ to |ar| and execute |_̄list|. The resulting output must not use vertical place. We must not forget to delete the list, or we will get the same bars on the next page again.
|| is the amount of place between the text margin and the change bars.
bars =2cc
We finish the macro file so that garbage (e.g. of exchanges between systems) can come afterwards.
`= `_=